/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.digest;

import java.awt.*;

/**
 * Converts hexadecimal Strings.
 * <p>
 * 基本原理：
 * byte 8 位，hex 4 位，因此 1 个字节，可以拆解成 2 个 16 进制数。
 * 进制转换，就是将 byte 高低 4 位转换成对饮字符。
 * <p>
 * 这个算法，存在一个缺陷，就是 byte 是有符号的，取值范围在 -256 ~ 255，
 * 在做纯数字转换时，实际放不下两个 16 进制数，比如 0xFF，实际无法赋值给 byte，
 * 0xFF 强转为 byte，用于位运算不会有问题，但是用于四则计算时，实际值为 -1。
 *
 * @author Mr.css 2018-07-13 11:25:54
 */
public class Hex {

    private Hex() {
    }

    /**
     * 转 16 进制色值
     *
     * @param r 红
     * @param g 绿
     * @param b 蓝
     * @return 16 进制色值
     */
    public static String encodeColor(int r, int g, int b) {
        char[] hexChars = new char[7];
        encodeRgb(r, g, b, hexChars);
        return new String(hexChars);
    }

    /**
     * 转 16 进制色值
     *
     * @param r 红
     * @param g 绿
     * @param b 蓝
     * @param a 透明度
     * @return 16 进制色值
     */
    public static String encodeColor(int r, int g, int b, int a) {
        char[] hexChars = new char[9];
        encodeRgb(r, g, b, hexChars);
        hexChars[7] = DIGITS[(a >> 4) & 0xF];
        hexChars[8] = DIGITS[a & 0xF];
        return new String(hexChars);
    }

    /**
     * 将 rgb 转为 16 进制
     *
     * @param r     红
     * @param g     绿
     * @param b     蓝
     * @param chars 字符数组
     */
    private static void encodeRgb(int r, int g, int b, char[] chars) {
        chars[0] = '#';
        chars[1] = DIGITS[(r >> 4) & 0xF];
        chars[2] = DIGITS[r & 0xF];
        chars[3] = DIGITS[(g >> 4) & 0xF];
        chars[4] = DIGITS[g & 0xF];
        chars[5] = DIGITS[(b >> 4) & 0xF];
        chars[6] = DIGITS[b & 0xF];
    }

    /**
     * 转 16 进制色值
     *
     * @param color -
     * @return 16 进制色值
     */
    public static String encodeColor(Color color) {
        return encodeColor(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     *
     * @param data a byte[] to convert to Hex characters
     * @return A String with lower case
     */
    public static String encodeToString(byte[] data) {
        char[] encodedChars = encode(data);
        return new String(encodedChars);
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     * One byte is 8 bits, each byte is divided into the high-4-bits and the low-4-bits,
     * so the returned array will be double the length of the passed array.
     * The value range of every 4-bits is 0-15, numeric match with {@link Hex#DIGITS} or {@link Hex#DIGITS_UPPER}.
     *
     * @param data     a byte[] to convert to Hex characters
     * @param toDigits the output alphabet
     * @return A char[] containing hexadecimal characters
     */
    protected static char[] encode(final byte[] data, final char[] toDigits) {
        int l = data.length;
        char[] out = new char[l << 1];
        for (int i = 0, j = 0; i < l; i++) {
            out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
            out[j++] = toDigits[0x0F & data[i]];
        }

        return out;
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     *
     * @param data a byte[] to convert to Hex characters
     * @return A char[] containing hexadecimal characters with lower case
     */
    public static char[] encode(final byte[] data) {
        return encode(data, DIGITS);
    }

    /**
     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
     *
     * @param data a byte[] to convert to Hex characters
     * @return A char[] containing hexadecimal characters with upper case
     */
    public static char[] encodeUpper(final byte[] data) {
        return encode(data, DIGITS_UPPER);
    }

    /**
     * decode
     *
     * @param hex An String containing hexadecimal digits
     * @return A byte array containing binary data decoded from the supplied char array.
     * @throws IllegalArgumentException Thrown if an odd number or illegal of characters is supplied
     */
    public static byte[] decode(String hex) {
        return decode(hex.toCharArray());
    }

    /**
     * decode
     *
     * @param data An array of characters containing hexadecimal digits
     * @return A byte array containing binary data decoded from the supplied char array.
     * @throws IllegalArgumentException Thrown if an odd number or illegal of characters is supplied
     */
    public static byte[] decode(char[] data) {
        int len = data.length;
        if ((len & 0x01) != 0) {
            throw new IllegalArgumentException("Odd number of characters.");
        }
        byte[] out = new byte[len >> 1];
        for (int i = 0, j = 0; j < len; ) {
            int f = toDigit(data[j], j) << 4;
            j++;
            f |= toDigit(data[j], j);
            j++;
            out[i] = (byte) (f & 255);
            i++;
        }

        return out;
    }

    /**
     * Converts a hexadecimal character to an integer.
     *
     * @param ch    A character to convert to an integer digit
     * @param index The index of the character in the source
     * @return An integer
     * @throws IllegalArgumentException Thrown if ch is an illegal hex character
     */
    protected static int toDigit(char ch, int index) {
        int digit = Character.digit(ch, 16);
        if (digit == -1) {
            throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index);
        } else {
            return digit;
        }
    }

    /**
     * Used to build output as Hex
     */
    private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * Used to build output as Hex
     */
    private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
}